#include <unistd.h>
#include <string>
#include <sstream>

#include "mandarava/linux/serial.hpp"

serial :: serial( const std::string& name ):
	m_name__( name ),
	m_fd__( -1 ),
	m_baud__( 115200 ),
	m_data_bits__( 8),
	m_parity__( parity:: NONE ),
	m_stop__( stop::ONE ),
	m_flow__( flow::NONE )
{
	if( name.empty() ){
		throw std::runtime_error( "请明确给出串口名称" );
	}
	if( access( name.c_str() , F_OK ) != 0 ){
		std::stringstream ss;
		ss << "指定名称 " << name <<" 的串口不存在";
		throw std::runtime_error( ss.str() );
	}
}

serial :: serial( const std::string& name , int baud , int dbits, stop s , parity p, flow f ):
	m_name__( name ),
	m_fd__( -1 ),
	m_baud__( baud ),
	m_data_bits__( dbits ),
	m_parity__( p ),
	m_stop__( s ),
	m_flow__( f )
{
	if( name.empty() ){
		throw std::runtime_error( "请明确给出串口名称" );
	}
	if( access( name.c_str() , F_OK ) != 0 ){
		std::stringstream ss;
		ss << "指定名称 " << name <<" 的串口不存在";
		throw std::runtime_error( ss.str() );
	}
}

bool serial :: open()
{
	int rc = ::open( m_name__.c_str() , O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY );
	if( rc < 0 ){
		ERROR_MSG( "打开串口错误" );
		return false;
	}

	update_param( m_baud__.load() , m_data_bits__.load() , m_stop__.load() , m_parity__.load() , m_flow__.load() );

	return true;
}

bool serial :: open( int baud , int dbits ,  stop s ,  parity p ,flow f )
{
	m_baud__ = baud;
	m_data_bits__ = dbits;
	m_stop__ = s;
	m_parity__ = p;
	m_flow__ = f;
	
	int rc = ::open( m_name__.c_str() , O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY );
	if( rc < 0 ){
		ERROR_MSG( "打开串口错误" );
		return false;
	}

	update_param( m_baud__.load() , m_data_bits__.load() , m_stop__.load() , m_parity__.load() , m_flow__.load() );
}

bool serial :: close()
{
	mandaravaCommand * cmd = mandaravaCommand::get();
	if( cmd ){ return; }
	
	bool ret = false;
	int rc = ::close( m_fd__.load );
	if( rc ){
		mandaravaEvt evt = { mandaravaEvtID::EVT_ERROR , this , buff_t( 200 ) };
		std::string err( "关闭接口失败" );
		memcpy( evt.m_buff.get() , err.c_str() , err.length() );
		evt.m_buff[ err.length() ] = '\0';
		evt.m_buff.dataLen( err.length() );
		
		cmd->send( evt );
		return ret;
	}
	
	mandaravaEvt evt = { mandaravaEvtID::EVT_CLOSE , this , buff_t( 0 ) };
	cmd->send( evt );
	
	return true;
}
   
size_t serial :: send( const char * data , size_t len )
{
	mandaravaCommand * cmd = mandaravaCommand::get();
	if( cmd ){ return; }
	
	if( data == nullptr || len == 0 ){
		mandaravaEvt evt = { mandaravaEvtID::EVT_ERROR , this , buff_t( 200 ) };
		std::string err( "要发送的数据内容不能为空" );
		memcpy( evt.m_buff.get() , err.c_str() , err.length() );
		evt.m_buff[ err.length() ] = '\0';
		evt.m_buff.dataLen( err.length() );
		cmd->send( evt );
	}
	size_t rc = write( m_fd__.load() , data , len );
	if( rc <= 0 ){
		mandaravaEvt evt = { mandaravaEvtID::EVT_ERROR , this , buff_t( 200 ) };
		std::string err( "数据发送失败" );
		memcpy( evt.m_buff.get() , err.c_str() , err.length() );
		evt.m_buff[ err.length() ] = '\0';
		evt.m_buff.dataLen( err.length() );
		cmd->send( evt );
	}
}

bool serial :: update_param( int baud , int dbits , stop s , parity p,  flow f )
{
	bool ret = false;

	struct termios newtio,oldtio;
	/*保存测试现有串口参数设置，在这里如果串口号等出错，会有相关的出错信息*/
	if  ( tcgetattr( fd,&oldtio)  !=  0) {
		ret = false;
	}
	bzero( &newtio, sizeof( newtio ) );
	/*步骤一，设置字符大小*/
	newtio.c_cflag  |=  CLOCAL | CREAD;
	newtio.c_cflag &= ~CSIZE;
	switch( char_len ){
	case 5:  newtio.c_cflag |= CS5;  break;
	case 6:  newtio.c_cflag |= CS6;  break;
	case 7:  newtio.c_cflag |= CS7;  break;
	case 8:  default: newtio.c_cflag |= CS8;  break;
	}
	/*设置奇偶校验位*/
	switch( p ) {
	case parity::ODD:
		newtio.c_cflag |= PARENB; newtio.c_iflag |= INPCK; newtio.c_cflag |= PARODD; break;
	case parity::EVEN:
		newtio.c_cflag |= PARENB; newtio.c_iflag |= INPCK; newtio.c_cflag &= ~PARODD; break;
	case parity::NONE:
	default:
		newtio.c_cflag &= ~PARENB;          break;
	}
	/*设置波特率*/
	switch( baud ) {
	case 2400:
		cfsetispeed(&newtio, B2400);           cfsetospeed(&newtio, B2400);            break;
	case 4800:
		cfsetispeed(&newtio, B4800);           cfsetospeed(&newtio, B4800);            break;
	case 9600:
		cfsetispeed(&newtio, B9600);           cfsetospeed(&newtio, B9600);            break;
	case 19200:
		cfsetispeed(&newtio, B19200);          cfsetospeed(&newtio, B19200);           break;
	case 38400:
		cfsetispeed(&newtio, B38400);          cfsetospeed(&newtio, B38400);           break;
	case 57600:
		cfsetispeed(&newtio, B57600);          cfsetospeed(&newtio, B57600);           break;
	case 115200:
		cfsetispeed(&newtio, B115200);         cfsetospeed(&newtio, B115200);          break;
	case 230400:
		cfsetispeed(&newtio, B230400);         cfsetospeed(&newtio, B230400);          break;
	case 460800:
		cfsetispeed(&newtio, B460800);         cfsetospeed(&newtio, B460800);          break;
	case 921600:
		cfsetispeed(&newtio, B921600);         cfsetospeed(&newtio, B921600);          break;
	case 3000000:
		cfsetispeed(&newtio, B3000000);        cfsetospeed(&newtio, B3000000);         break;
	default:
		cfsetispeed(&newtio, baud );         cfsetospeed(&newtio, baud );          break;
	}

	if( s == stop::ONE )                    /*设置停止位*/
		newtio.c_cflag &=  ~CSTOPB;
	else if ( s == stop::TWO )
		newtio.c_cflag |=  CSTOPB;

	newtio.c_cc[VTIME]  = 0;                /*设置等待时间和最小接收字符*/
	newtio.c_cc[VMIN] = 0;

	tcflush( fd,TCIFLUSH );                       /*处理未接收字符*/

	if((tcsetattr( fd,TCSANOW,&newtio))!=0){     /*激活新配置*/
		ret = false;
	}
	else ret = true;

	return ret;
}

nativeH_t serial :: native_handle()
{
	return m_fd__.load();
}

void serial :: onReadyRead( const uint8_t* data , size_t len )
{
	UNUSED( data );
	UNUSED( len );

	mandaravaCommand * cmd = mandaravaCommand::get();
	if( cmd ){ return; }
	
	mandaravaEvt evt = { mandaravaEvtID::EVT_DATA , this , buff_t( 1024) };

	size_t len1 = ::read( m_fd__.load() , evt.m_buff.get() , 1023 );
	if( len1 <= 0 ){
		return;
	}

	evt.m_buff.dataLen( len1 );
	// 发出读取到串口数据的通知
	cmd->send( evt );
}
